/*
 * Legal Notice
 *
 * This document and associated source code (the "Work") is a part of a
 * benchmark specification maintained by the TPC.
 *
 * The TPC reserves all right, title, and interest to the Work as provided
 * under U.S. and international laws, including without limitation all patent
 * and trademark rights therein.
 *
 * No Warranty
 *
 * 1.1 TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE INFORMATION
 *     CONTAINED HEREIN IS PROVIDED "AS IS" AND WITH ALL FAULTS, AND THE
 *     AUTHORS AND DEVELOPERS OF THE WORK HEREBY DISCLAIM ALL OTHER
 *     WARRANTIES AND CONDITIONS, EITHER EXPRESS, IMPLIED OR STATUTORY,
 *     INCLUDING, BUT NOT LIMITED TO, ANY (IF ANY) IMPLIED WARRANTIES,
 *     DUTIES OR CONDITIONS OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR
 *     PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OF
 *     WORKMANLIKE EFFORT, OF LACK OF VIRUSES, AND OF LACK OF NEGLIGENCE.
 *     ALSO, THERE IS NO WARRANTY OR CONDITION OF TITLE, QUIET ENJOYMENT,
 *     QUIET POSSESSION, CORRESPONDENCE TO DESCRIPTION OR NON-INFRINGEMENT
 *     WITH REGARD TO THE WORK.
 * 1.2 IN NO EVENT WILL ANY AUTHOR OR DEVELOPER OF THE WORK BE LIABLE TO
 *     ANY OTHER PARTY FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO THE
 *     COST OF PROCURING SUBSTITUTE GOODS OR SERVICES, LOST PROFITS, LOSS
 *     OF USE, LOSS OF DATA, OR ANY INCIDENTAL, CONSEQUENTIAL, DIRECT,
 *     INDIRECT, OR SPECIAL DAMAGES WHETHER UNDER CONTRACT, TORT, WARRANTY,
 *     OR OTHERWISE, ARISING IN ANY WAY OUT OF THIS OR ANY OTHER AGREEMENT
 *     RELATING TO THE WORK, WHETHER OR NOT SUCH AUTHOR OR DEVELOPER HAD
 *     ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * Contributors
 * - Sergey Vasilevskiy, Matt Emmerton, Doug Johnson
 */

//
// Defines the entry point for the console application.
//

#include "../inc/EGenLoader_stdafx.h"

using namespace TPCE;

// Driver Defaults
TIdent              iStartFromCustomer = iDefaultStartFromCustomer;
TIdent              iCustomerCount = iDefaultCustomerCount;         // # of customers for this instance
TIdent              iTotalCustomerCount = iDefaultCustomerCount;    // total number of customers in the database
UINT                iLoadUnitSize = iDefaultLoadUnitSize;           // # of customers in one load unit
UINT                iScaleFactor = 500;                             // # of customers for 1 tpsE
UINT                iDaysOfInitialTrades = 300;

// These flags are used to control which tables get generated and loaded.
bool                bTableGenerationFlagNotSpecified = true;    // assume no flag is specified.
bool                bGenerateFixedTables = false;
bool                bGenerateGrowingTables = false;
bool                bGenerateScalingTables = false;
bool                bGenerateUsingCache = true;

char                szInDir[iMaxPath];
#ifdef COMPILE_FLAT_FILE_LOAD
char                szOutDir[iMaxPath];
FlatFileOutputModes     FlatFileOutputMode;
#endif
#ifdef COMPILE_ODBC_LOAD
char                szServer[iMaxHostname];
char                szDB[iMaxDBName];
#endif
#if defined(COMPILE_ODBC_LOAD) || defined(COMPILE_CUSTOM_LOAD)
char                szLoaderParms[1024];
#endif

#ifndef FLAT_IN_PATH
#define FLAT_IN_PATH "flat_in/"
#endif

#ifndef FLAT_OUT_PATH
#define FLAT_OUT_PATH "flat_out/"
#endif

enum eLoadImplementation {
    NULL_LOAD = 0,  // no load - generate data only
    FLAT_FILE_LOAD,
    ODBC_LOAD,
    CUSTOM_LOAD     // sponsor-provided load mechanism
};
#if defined(DEFAULT_LOAD_TYPE)
eLoadImplementation LoadType = DEFAULT_LOAD_TYPE;
#elif defined(COMPILE_FLAT_FILE_LOAD)
eLoadImplementation LoadType = FLAT_FILE_LOAD;
#elif defined(COMPILE_ODBC_LOAD)
eLoadImplementation LoadType = ODBC_LOAD;
#elif defined(COMPILE_CUSTOM_LOAD)
eLoadImplementation LoadType = CUSTOM_LOAD;
#else
eLoadImplementation LoadType = NULL_LOAD;
#endif

/*
 * Prints program usage to std error.
 */
void Usage()
{
  fprintf( stderr, "Usage:\n" );
  fprintf( stderr, "EGenLoader [options] \n\n" );
  fprintf( stderr, " Where\n" );
  fprintf( stderr, "  Option                       Default     Description\n" );
  fprintf( stderr, "   -b number                   %" PRId64 "           Beginning customer ordinal position\n", iStartFromCustomer);
  fprintf( stderr, "   -c number                   %" PRId64 "        Number of customers (for this instance)\n", iCustomerCount );
  fprintf( stderr, "   -t number                   %" PRId64 "        Number of customers (total in the database)\n", iTotalCustomerCount );
  fprintf( stderr, "   -f number                   %d         Scale factor (customers per 1 tpsE)\n", iScaleFactor );
  fprintf( stderr, "   -w number                   %d          Number of Workdays (8-hour days) of \n", iDaysOfInitialTrades );
  fprintf( stderr, "                                           initial trades to populate\n" );
  fprintf( stderr, "   -i dir                      %-11s Directory for input files\n", FLAT_IN_PATH );
  fprintf( stderr, "   -l [FLAT|ODBC|CUSTOM|NULL]  FLAT        Type of load\n" );
#ifdef COMPILE_FLAT_FILE_LOAD
  fprintf( stderr, "   -m [APPEND|OVERWRITE]       OVERWRITE   Flat File output mode\n" );
  fprintf( stderr, "   -o dir                      %-11s Directory for output files\n", FLAT_OUT_PATH );
#endif
#ifdef COMPILE_ODBC_LOAD
  fprintf( stderr, "   -s string                   localhost   Database server\n" );
  fprintf( stderr, "   -d string                   tpce        Database name\n" );
#endif
#if defined(COMPILE_ODBC_LOAD) || defined(COMPILE_CUSTOM_LOAD)
  fprintf( stderr, "   -p string                               Additional parameters to loader\n" );
#endif
  fprintf( stderr, "\n" );
  fprintf( stderr, "   -x                          -x          Generate all tables\n");
  fprintf( stderr, "   -xf                                     Generate all fixed-size tables\n");
  fprintf( stderr, "   -xd                                     Generate all scaling and growing tables\n");
  fprintf( stderr, "                                           (equivalent to -xs -xg)\n");
  fprintf( stderr, "   -xs                                     Generate scaling tables\n");
  fprintf( stderr, "                                           (except BROKER)\n");
  fprintf( stderr, "   -xg                                     Generate growing tables and BROKER\n");
  fprintf( stderr, "   -g                                      Disable caching when generating growing tables\n" );
}

void ParseCommandLine( int argc, char *argv[] )
{
int   arg;
char  *sp;
char  *vp;

  /*
   *  Scan the command line arguments
   */
  for ( arg = 1; arg < argc; ++arg ) {

    /*
     *  Look for a switch
     */
    sp = argv[arg];
    if ( *sp == '-' ) {
      ++sp;
    }
    *sp = (char)tolower( *sp );

    /*
     *  Find the switch's argument.  It is either immediately after the
     *  switch or in the next argv
     */
    vp = sp + 1;
    // Allow for switched that don't have any parameters.
    // Need to check that the next argument is in fact a parameter
    // and not the next switch that starts with '-'.
    //
    if ( (*vp == 0) && ((arg + 1) < argc) && (argv[arg + 1][0] != '-') )
    {
        vp = argv[++arg];
    }

    /*
     *  Parse the switch
     */
    switch ( *sp ) {
      case 'b':
        sscanf(vp, "%"PRId64, &iStartFromCustomer);
        if (iStartFromCustomer <= 0)
        {   // set back to default
            // either number parsing was unsuccessful
            // or a bad value was specified
            iStartFromCustomer = iDefaultStartFromCustomer;
        }
        break;
      case 'c':
        sscanf(vp, "%"PRId64, &iCustomerCount);
        break;
      case 't':
        sscanf(vp, "%"PRId64, &iTotalCustomerCount);
        break;
      case 'f':
        iScaleFactor = (UINT) atoi( vp );
        break;
      case 'w':
    iDaysOfInitialTrades = (UINT) atoi( vp );
        break;

      case 'i': // Location of input files.
          strncpy(szInDir, vp, sizeof(szInDir));
          if(( '/' != szInDir[ strlen(szInDir) - 1 ] ) && ( '\\' != szInDir[ strlen(szInDir) - 1 ] ))
          {
              strncat( szInDir, "/", sizeof(szInDir) );
          }
          break;

      case 'l': // Load type.
#ifdef COMPILE_FLAT_FILE_LOAD
          if( 0 == strcmp( vp, "FLAT" ))
          {
              LoadType = FLAT_FILE_LOAD;
              break;
          }
#endif
#ifdef COMPILE_ODBC_LOAD
          if( 0 == strcmp( vp, "ODBC" ))
          {
              LoadType = ODBC_LOAD;
              break;
          }
#endif
          if ( 0 == strcmp( vp, "NULL" ))
          {
              LoadType = NULL_LOAD;
              break;
          }
#ifdef COMPILE_CUSTOM_LOAD
          if ( 0 == strcmp( vp, "CUSTOM" ))
          {
              LoadType = CUSTOM_LOAD;
              break;
          }
#endif
          Usage();
          exit( ERROR_BAD_OPTION );
          break;

#ifdef COMPILE_FLAT_FILE_LOAD
      case 'm': // Mode for output of flat files.
          if( 0 == strcmp( vp, "APPEND" ))
          {
              FlatFileOutputMode = FLAT_FILE_OUTPUT_APPEND;
          }
          else if( 0 == strcmp( vp, "OVERWRITE" ))
          {
              FlatFileOutputMode = FLAT_FILE_OUTPUT_OVERWRITE;
          }
          else
          {
            Usage();
            exit( ERROR_BAD_OPTION );
          }
          break;

      case 'o': // Location for output files.
          strncpy(szOutDir, vp, sizeof(szOutDir));
          if(( '/' != szOutDir[ strlen(szOutDir) - 1 ] ) && ( '\\' != szOutDir[ strlen(szOutDir) - 1 ] ))
          {
              strncat( szOutDir, "/", sizeof(szOutDir) );
          }
          break;
#endif
#ifdef COMPILE_ODBC_LOAD
      case 's': // Database server name.
          strncpy(szServer, vp, sizeof(szServer));
          break;

      case 'd': // Database name.
          strncpy(szDB, vp, sizeof(szDB));
          break;
#endif
#if defined(COMPILE_ODBC_LOAD) || defined(COMPILE_CUSTOM_LOAD)
      case 'p': // Loader Parameters
          strncpy(szLoaderParms, vp, sizeof(szLoaderParms));
          break;
#endif
      case 'g': // Disable Cache for Growing Tables (TradeGen)
          bGenerateUsingCache = false;
          break;
      case 'x': // Table Generation
          bTableGenerationFlagNotSpecified = false; //A -x flag has been used

          if( NULL == vp || *vp == '\0')
          {
              bGenerateFixedTables = true;
              bGenerateGrowingTables = true;
              bGenerateScalingTables = true;
          }
          else if( 0 == strcmp( vp, "f" ))
          {
              bGenerateFixedTables = true;
          }
          else if( 0 == strcmp( vp, "g" ))
          {
              bGenerateGrowingTables = true;
          }
          else if( 0 == strcmp( vp, "s" ))
          {
              bGenerateScalingTables = true;
          }
          else if( 0 == strcmp( vp, "d" ))
          {
              bGenerateGrowingTables = true;
              bGenerateScalingTables = true;
          }
          else
          {
              Usage();
              exit( ERROR_BAD_OPTION );
          }
          break;

      default:
        Usage();
        fprintf( stderr, "Error: Unrecognized option: %s\n",sp);
        exit( ERROR_BAD_OPTION );
    }
  }

}

/*
* This function validates EGenLoader parameters that may have been
* specified on the command line. It's purpose is to prevent passing of
* parameter values that would cause the loader to produce incorrect data.
*
* Condition: needs to be called after ParseCommandLine.
*
* PARAMETERS:
*       NONE
*
* RETURN:
*       true                    - if all parameters are valid
*       false                   - if at least one parameter is invalid
*/
bool ValidateParameters()
{
    bool bRet = true;

    // Starting customer must be a non-zero integral multiple of load unit size + 1.
    //
    if ((iStartFromCustomer % iLoadUnitSize) != 1)
    {
        cout << "The specified starting customer (-b " << iStartFromCustomer
            << ") must be a non-zero integral multiple of the load unit size ("
            << iLoadUnitSize << ") + 1." << endl;

        bRet = false;
    }

    // Total number of customers in the database cannot be less
    // than the number of customers for this loader instance
    //
    if (iTotalCustomerCount < iStartFromCustomer + iCustomerCount - 1)
    {
        iTotalCustomerCount = iStartFromCustomer + iCustomerCount - 1;
    }

    // Customer count must be a non-zero integral multiple of load unit size.
    //
    if ((iLoadUnitSize > iCustomerCount) || (0 != iCustomerCount % iLoadUnitSize))
    {
        cout << "The specified customer count (-c " << iCustomerCount
            << ") must be a non-zero integral multiple of the load unit size ("
            << iLoadUnitSize << ")." << endl;

        bRet = false;
    }

    // Total customer count must be a non-zero integral multiple of load unit size.
    //
    if ((iLoadUnitSize > iTotalCustomerCount) || (0 != iTotalCustomerCount % iLoadUnitSize))
    {
        cout << "The total customer count (-t " << iTotalCustomerCount
            << ") must be a non-zero integral multiple of the load unit size ("
            << iLoadUnitSize << ")." << endl;

        bRet = false;
    }

    // Completed trades in 8 hours must be a non-zero integral multiple of 100
    // so that exactly 1% extra trade ids can be assigned to simulate aborts.
    //
    if ((INT64)(HoursPerWorkDay * SecondsPerHour * iLoadUnitSize / iScaleFactor) % 100 != 0)
    {
        cout << "Incompatible value for Scale Factor (-f) specified." << endl;
        cout << HoursPerWorkDay << " * " << SecondsPerHour << " * Load Unit Size (" << iLoadUnitSize
            << ") / Scale Factor (" << iScaleFactor
            << ") must be integral multiple of 100." << endl;

        bRet = false;
    }

    if (iDaysOfInitialTrades == 0)
    {
        cout << "The specified number of 8-Hour Workdays (-w "
             << (iDaysOfInitialTrades) << ") must be non-zero." << endl;

        bRet = false;
    }

    return bRet;
}

/*
*   Create loader class factory corresponding to the
*   selected load type.
*/
CBaseLoaderFactory* CreateLoaderFactory(eLoadImplementation eLoadType)
{
    switch (eLoadType)
    {
    case NULL_LOAD:
        return new CNullLoaderFactory();

#ifdef COMPILE_ODBC_LOAD
    case ODBC_LOAD:
        return new CODBCLoaderFactory(szServer, szDB, szLoaderParms);
#endif  // #ifdef COMPILE_ODBC_LOAD

#ifdef COMPILE_FLAT_FILE_LOAD
    case FLAT_FILE_LOAD:
        return new CFlatLoaderFactory(szOutDir, FlatFileOutputMode);
#endif  //#ifdef COMPILE_FLAT_FILE_LOAD

#ifdef COMPILE_CUSTOM_LOAD
    case CUSTOM_LOAD:
        return new CCustomLoaderFactory(szLoaderParms);
#endif  //#ifdef COMPILE_CUSTOM_FILE_LOAD

    default:
        assert(false);  // this should never happen
    }

    return NULL;    //should never happen
}

int main(int argc, char* argv[])
{
    CInputFiles                     inputFiles;
    CBaseLoaderFactory*             pLoaderFactory; // class factory that creates table loaders
    CGenerateAndLoadStandardOutput  Output;
    CGenerateAndLoad*               pGenerateAndLoad;
    CDateTime                       StartTime, EndTime, LoadTime;   // to time the database load

    // Output EGen version
    PrintEGenVersion();

    // Establish defaults for command line options.
#ifdef COMPILE_ODBC_LOAD
    strncpy(szServer, "localhost", sizeof(szServer));
    strncpy(szDB, "tpce", sizeof(szDB));
#endif
    strncpy(szInDir, FLAT_IN_PATH, sizeof(szInDir));
#ifdef COMPILE_FLAT_FILE_LOAD
    strncpy(szOutDir, FLAT_OUT_PATH, sizeof(szOutDir));
    FlatFileOutputMode = FLAT_FILE_OUTPUT_OVERWRITE;
#endif

    // Get command line options.
    //
    ParseCommandLine(argc, argv);

    // Validate global parameters that may have been modified on
    // the command line.
    //
    if (!ValidateParameters())
    {
        return ERROR_INVALID_OPTION_VALUE;  // exit from the loader returning a non-zero code
    }

    // Let the user know what settings will be used.
    cout<<endl<<endl<<"Using the following settings."<<endl<<endl;
    switch ( LoadType ) {
#ifdef COMPILE_ODBC_LOAD
    case ODBC_LOAD:
        cout<<"\tLoad Type:\t\tODBC"<<endl;
        cout<<"\tServer Name:\t\t"<<szServer<<endl;
        cout<<"\tDatabase:\t\t"<<szDB<<endl;
        break;
#endif
#ifdef COMPILE_FLAT_FILE_LOAD
    case FLAT_FILE_LOAD:
        cout<<"\tLoad Type:\t\tFlat File"<<endl;
        switch ( FlatFileOutputMode ) {
        case FLAT_FILE_OUTPUT_APPEND:
            cout<<"\tOutput Mode:\t\tAPPEND"<<endl;
            break;
        case FLAT_FILE_OUTPUT_OVERWRITE:
            cout<<"\tOutput Mode:\t\tOVERWRITE"<<endl;
            break;
        }
        cout<<"\tOut Directory:\t\t"<<szOutDir<<endl;
        break;
#endif
#ifdef COMPILE_CUSTOM_LOAD
    case CUSTOM_LOAD:
        cout<<"\tLoad Type:\t\tCustom"<<endl;
        cout<<"\tLoader Parms:\t\t"<<szLoaderParms<<endl;
        break;
#endif
    case NULL_LOAD:
    default:
        cout<<"\tLoad Type:\t\tNull load (generation only)"<<endl;
        break;
    }
    cout<<"\tIn Directory:\t\t"<<szInDir<<endl;
    cout<<"\tStart From Customer:\t"<<iStartFromCustomer<<endl;
    cout<<"\tCustomer Count:\t\t"<<iCustomerCount<<endl;
    cout<<"\tTotal customers:\t"<<iTotalCustomerCount<<endl;
    cout<<"\tLoad Unit:\t\t"<<iLoadUnitSize<<endl;
    cout<<"\tScale Factor:\t\t"<<iScaleFactor<<endl;
    cout<<"\tInitial Trade Days:\t"<<iDaysOfInitialTrades<<endl;
    cout<<"\tCaching Enabled:\t"<<(bGenerateUsingCache?"true":"false")<<endl;
    cout<<endl<<endl;

    // Know the load type => create the loader factory.
    //
    pLoaderFactory = CreateLoaderFactory(LoadType);

    try {
        // Create a unique log name for this loader instance.
        // Simultaneous loader instances are to be run with
        // disjoint customer ranges, so use this fact to
        // construct a unique name.
        //
        char szLogFileName[64];

        snprintf(&szLogFileName[0], sizeof(szLogFileName),
                 "EGenLoaderFrom%" PRId64 "To%" PRId64 ".log",
                 iStartFromCustomer, (iStartFromCustomer + iCustomerCount)-1);

        // Create log formatter and logger instance
        //
        CLogFormatTab fmt;
        CEGenLogger logger(eDriverEGenLoader, 0, szLogFileName, &fmt);

        // Load all of the input files into memory.
        inputFiles.Initialize(eDriverEGenLoader, iTotalCustomerCount, iTotalCustomerCount, szInDir);

        // Create the main class instance
        pGenerateAndLoad = new CGenerateAndLoad(inputFiles, iCustomerCount, iStartFromCustomer,
                                                iTotalCustomerCount, iLoadUnitSize,
                                                iScaleFactor, iDaysOfInitialTrades,
                                                pLoaderFactory, &logger, &Output, szInDir,
                                                bGenerateUsingCache
                                               );

        //  The generate and load phase starts here.
        //
        StartTime.SetToCurrent();

        // Generate static tables if appropriate.
        if(( bTableGenerationFlagNotSpecified && ( iStartFromCustomer == iDefaultStartFromCustomer )) || bGenerateFixedTables )
        {
            pGenerateAndLoad->GenerateAndLoadFixedTables();
        }

        // Generate dynamic scaling tables if appropriate.
        if( bTableGenerationFlagNotSpecified || bGenerateScalingTables )
        {
            pGenerateAndLoad->GenerateAndLoadScalingTables();
        }
        // Generate dynamic trade tables if appropriate.
        if( bTableGenerationFlagNotSpecified || bGenerateGrowingTables )
        {
            pGenerateAndLoad->GenerateAndLoadGrowingTables();
        }

        //  The generate and load phase ends here.
        //
        EndTime.SetToCurrent();

        LoadTime.Set(0);    // clear time
        LoadTime.Add(0, (int)((EndTime - StartTime) * MsPerSecond));    // add ms

        cout<<endl<<"Generate and load time: "<<LoadTime.ToStr(01)<<endl<<endl;

        delete pGenerateAndLoad;    // don't really need to do that, but just for good style
        delete pLoaderFactory;      // don't really need to do that, but just for good style

    }
    catch (CBaseErr& err)
    {
        cout<<endl<<"Error "<<err.ErrorNum()<<": "<<err.ErrorText();
        if (err.ErrorLoc()) {
            cout<<" at "<<err.ErrorLoc()<<endl;
        } else {
            cout<<endl;
        }
        return 1;
    }
    // operator new will throw std::bad_alloc exception if there is no sufficient memory for the request.
    //
    catch (std::bad_alloc&)
    {
        cout<<endl<<endl<<"*** Out of memory ***"<<endl;
        return 2;
    }
    catch (std::exception& err)
    {
        cout << endl << endl <<"Caught Exception: " << err.what() << endl;
    }
    catch (...)
    {
        cout << endl << endl <<"Caught Unknown Exception" << endl;
    }

    return 0;
}
